/*
 *  Copyright (c) 2020 Arthur Milchior <arthur@milchior.fr>
 *
 *  This program is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation; either version 3 of the License, or (at your option) any later
 *  version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *  PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.ichi2.libanki

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.CollectionHelper
import com.ichi2.anki.RobolectricTest
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.Matchers.equalTo
import org.junit.Assert.*
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*

@RunWith(AndroidJUnit4::class)
class CollectionTest : RobolectricTest() {
    @Test
    fun editClozeGenerateCardsInSameDeck() {
        // #7781
        // Technically, editing a card with conditional fields can also cause this, but cloze cards are much more common
        val n = addNoteUsingModelName("Cloze", "{{c1::Hello}} {{c2::World}}", "Extra")
        val did = addDeck("Testing")
        for (c in n.cards()) {
            c.did = did
            c.flush()
        }
        assertThat("two cloze notes should be generated", n.numberOfCards(), equalTo(2))

        // create card 3
        n.setField(0, n.fields[0].toString() + "{{c3::third}}")
        n.flush()
        assertThat("A new card should be generated", n.numberOfCards(), equalTo(3))
        assertThat("The new card should have the same did as the previous cards", n.cards()[2].did, equalTo(did))
    }

    @Test
    fun beforeUploadClosesCollection() {
        val col = col
        assertThat("db should be open", CollectionHelper.instance.colIsOpen(), equalTo(true))
        col.beforeUpload()
        assertThat("db should be closed", CollectionHelper.instance.colIsOpen(), equalTo(false))
    }

    /*******************
     ** autogenerated from https://github.com/ankitects/anki/blob/2c73dcb2e547c44d9e02c20a00f3c52419dc277b/pylib/tests/test_cards.py *
     *******************/
    /*TODO
      @Test
      public void test_create_open(){
      (fd, path) = tempfile.mkstemp(suffix=".anki2", prefix="test_attachNew");
      try {
      os.close(fd);
      os.unlink(path);
      } catch (OSError) {
      }
      Collection col = aopen(path);
      // for open()
      String newPath = col.getPath();
      long newMod = col.getMod();
      col.close();

      // reopen
      col = aopen(newPath);
      assertEquals(newMod, col.getMod());
      col.close();

      // non-writeable dir
      if (isWin) {
      String dir = "c:\root.anki2";
      } else {
      String dir = "/attachroot.anki2";
      }
      assertException(Exception, lambda: aopen(dir));
      // reuse tmp file from before, test non-writeable file
      os.chmod(newPath, 0);
      assertException(Exception, lambda: aopen(newPath));
      os.chmod(newPath, 0o666);
      os.unlink(newPath);
      } */
    @Test
    fun test_noteAddDelete() {
        val col = col
        // add a note
        var note = col.newNote()
        note.setItem("Front", "one")
        note.setItem("Back", "two")
        var n = col.addNote(note)
        assertEquals(1, n)
        // test multiple cards - add another template
        val m = col.models.current()
        val mm = col.models
        val t = Models.newTemplate("Reverse")
        t.put("qfmt", "{{Back}}")
        t.put("afmt", "{{Front}}")
        mm.addTemplateModChanged(m!!, t)
        mm.save(m, true) // todo: remove true which is not upstream
        assertEquals(2, col.cardCount())
        // creating new notes should use both cards
        note = col.newNote()
        note.setItem("Front", "three")
        note.setItem("Back", "four")
        n = col.addNote(note)
        assertEquals(2, n)
        assertEquals(4, col.cardCount())
        // check q/a generation
        val c0 = note.cards()[0]
        assertThat(c0.q(), Matchers.containsString("three"))
        // it should not be a duplicate
        assertEquals(note.dupeOrEmpty(), Note.DupeOrEmpty.CORRECT)
        // now let's make a duplicate
        val note2 = col.newNote()
        note2.setItem("Front", "one")
        note2.setItem("Back", "")
        assertNotEquals(note2.dupeOrEmpty(), Note.DupeOrEmpty.CORRECT)
        // empty first field should not be permitted either
        note2.setItem("Front", " ")
        assertNotEquals(note2.dupeOrEmpty(), Note.DupeOrEmpty.CORRECT)
    }

    @Test
    @Ignore("I don't understand this csum")
    fun test_fieldChecksum() {
        val col = col
        val note = col.newNote()
        note.setItem("Front", "new")
        note.setItem("Back", "new2")
        col.addNote(note)
        assertEquals(-0xc2a6b03f, col.db.queryLongScalar("select csum from notes"))
        // changing the val should change the checksum
        note.setItem("Front", "newx")
        note.flush()
        assertEquals(0x302811ae, col.db.queryLongScalar("select csum from notes"))
    }

    @Test
    fun test_addDelTags() {
        val col = col
        val note = col.newNote()
        note.setItem("Front", "1")
        col.addNote(note)
        val note2 = col.newNote()
        note2.setItem("Front", "2")
        col.addNote(note2)
        // adding for a given id
        col.tags.bulkAdd(listOf(note.id), "foo")
        note.load()
        note2.load()
        assertTrue(note.tags.contains("foo"))
        assertFalse(note2.tags.contains("foo"))
        // should be canonified
        col.tags.bulkAdd(listOf(note.id), "foo aaa")
        note.load()
        assertEquals("aaa", note.tags[0])
        assertEquals(2, note.tags.size)
    }

    @Test
    fun test_timestamps() {
        val col = col
        val stdModelSize = StdModels.STD_MODELS.size
        assertEquals(col.models.all().size, stdModelSize)
        for (i in 0..99) {
            StdModels.BASIC_MODEL.add(col)
        }
        assertEquals(col.models.all().size, (100 + stdModelSize))
    }

    @Test
    @Ignore("Pending port of media search from Rust code")
    fun test_furigana() {
        val col = col
        val mm = col.models
        val m = mm.current()
        // filter should work
        m!!.getJSONArray("tmpls").getJSONObject(0).put("qfmt", "{{kana:Front}}")
        mm.save(m)
        val n = col.newNote()
        n.setItem("Front", "foo[abc]")
        col.addNote(n)
        val c = n.cards()[0]
        assertTrue(c.q().endsWith("abc"))
        // and should avoid sound
        n.setItem("Front", "foo[sound:abc.mp3]")
        n.flush()
        val question = c.q(true)
        assertThat("Question «$question» does not contains «anki:play».", question, Matchers.containsString("anki:play"))
        // it shouldn't throw an error while people are editing
        m.getJSONArray("tmpls").getJSONObject(0).put("qfmt", "{{kana:}}")
        mm.save(m)
        c.q(true)
    }

    @Test
    fun test_filterToValidCards() {
        val col = col
        val cid = addNoteUsingBasicModel("foo", "bar").firstCard().id
        assertEquals(ArrayList(setOf(cid)), col.filterToValidCards(longArrayOf(cid, cid + 1)))
    }
}
